//
// Created by denglibin on 19-5-8.
// 矩阵 变换
//
#include "settings.h"
#include "shader.h"
#include "buffer_obj.h"
#include "init.h"
#include "call_back.h"
#include "input.h"
#include "texture.h"
#include <GLFW/glfw3.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>

//变换

/**
 * 齐次坐标(Homogeneous Coordinates):
 向量的w分量也叫齐次坐标。想要从齐次向量得到3D向量，我们可以把x、y和z坐标分别除以w坐标。我们通常不会注意这个问题，因为w分量通常是1.0。
 使用齐次坐标有几点好处：它允许我们在3D向量上进行位移（如果没有w分量我们是不能位移向量的）
 *
 *GLM:
 * OpenGL没有自带任何的矩阵和向量知识，所以我们必须定义自己的数学类和函数。在教程中我们更希望抽象所有的数学细节，使用已经做好了的数学库。
 * 幸运的是，有个易于使用，专门为OpenGL量身定做的数学库，那就是GLM。
 * GLM库从0.9.9版本起，默认会将矩阵类型初始化为一个零矩阵（所有元素均为0），而不是单位矩阵（对角元素为1，其它元素为0）。
 * 如果你使用的是0.9.9或0.9.9以上的版本，你需要将所有的矩阵初始化改为 glm::mat4 mat = glm::mat4(1.0f)
 *
 */
static  void testGlm(){
    glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); //向量 x,y,z,w(w为1)
// 译注：下面就是矩阵初始化的一个例子，如果使用的是0.9.9及以上版本
    glm::mat4 trans = glm::mat4(1.0f); //单位矩阵(变换矩阵)
    trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f)); //位移(1,1,0)个单位的变换矩阵
    vec = trans * vec; //变换矩阵乘以向量(不满足交换律)得到变换后向量
    std::cout << vec.x << ","<< vec.y << ","<< vec.z << std::endl; //正确结果 2,1,0
}


/**
 * 创建缓冲对象
 * @param vertices
 * @param indices
 */
static void createBufferObj(unsigned  int * VBO, unsigned  int * EBO,float vertices[], int vSize, unsigned  int indices[], int iSize){
    *VBO = createVBO(vertices, vSize); //顶点缓冲对象
    *EBO = createEBO(indices, iSize); //索引缓冲对象
}

/**
 * 设置着色器程序变量的值
 * @param shaderProgramId 着色器程序id
 */
static void setUniformValue(int shaderProgramId){
    float timeValue = glfwGetTime(); //获取运行的秒数
    float greenValue = (sin(timeValue) / 2.0f) + 0.5f; //按正弦曲线变化(周期函数 有循环效果)
    float redValue = (sin(timeValue)) + 0.5f; //按正弦曲线变化(周期函数 有循环效果)
    set4Float(shaderProgramId, "ourColor", redValue, greenValue, 0.0f, 1.0f);

    //设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
    setInt(shaderProgramId, "texture1", 0);
    setInt(shaderProgramId, "texture2", 1);

    //变换矩阵
    glm::mat4 trans = glm::mat4(1.0f); //单位矩阵(变换矩阵)
   // trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0)); //按z轴逆时针旋转90度
    trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5)); //x,y,z都缩放为原来的0.5倍

    //随时间变换(循环中调用 setUniformValue)
    //trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f)); //往右下方移动
    trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f)); //旋转(每次调用该变换矩阵旋转的角度都会增大,达到一直旋转的效果)

    //设置uniform变量的值
    unsigned int transformLoc = glGetUniformLocation(shaderProgramId, "transform");
    /**
     * 第一个参数你现在应该很熟悉了，它是uniform的位置值。
     * 第二个参数告诉OpenGL我们将要发送多少个矩阵，这里是1。
     * 第三个参数询问我们我们是否希望对我们的矩阵进行置换(Transpose)，也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局，
     *      叫做列主序(Column-major Ordering)布局。GLM的默认布局就是列主序，所以并不需要置换矩阵，我们填GL_FALSE。
     * 最后一个参数是真正的矩阵数据，但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种，因此我们要先用GLM的自带的函数value_ptr来变换这些数据
     */
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
}

static void show(){
    //顶点数据----------------------------------------------------------------
    float vertices[] = {
            //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
            0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
            0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
            -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
            -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
    };
    //索引数据
    unsigned int indices[] = { // 注意索引从0开始!
            0, 1, 3, // 第一个三角形
            1, 2, 3  // 第二个三角形
    };
    //初始化
    init();
    //创建窗口对象
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, TITLE, NULL, NULL);
    if (window == NULL)
    {
        printf("Failed to create GLFW window");
        glfwTerminate();
        exit(-1);
    }
    //通知GLFW将窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    //对窗口注册一个回调函数,每当窗口改变大小，GLFW会调用这个函数并填充相应的参数供你处理
    glfwSetFramebufferSizeCallback(window, frameSizeChangeCallBack);

    //初始化GLAD用来管理OpenGL的函数指针
    //GLFW给我们的是glfwGetProcAdress，它根据我们编译的系统定义了正确的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        printf("Failed to initialize GLAD");
        exit(-1);
    }
    //在开始渲染之前必须告诉OpenGL渲染窗口(Viewport)的尺寸大小，这样OpenGL才能知道怎样根据窗口大小显示数据和坐标。
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    //创建着色器程序
    ShaderProgram shaderProgram = {0, "shader_code/vertex03.vs", "shader_code/fragment03.fs"};
    createShaderProgram(&shaderProgram);
    //创建顶点数组对象
    unsigned int VAO; //顶点属性数组对象id
    glGenVertexArrays(1, &VAO); //创建顶点数组对象
    //绑定VAO
    //顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定，任何随后的顶点属性调用都会储存在这个VAO中
    glBindVertexArray(VAO);
    //创建缓冲顶点缓冲对象和索引对象
    unsigned  int VBO,EBO;
    createBufferObj(&VBO, &EBO, vertices, sizeof(vertices), indices, sizeof(indices));

    //创建纹理对象
    unsigned  int texture1 = createTextureObj("resources/container.jpg", 0, GL_RGB);
    unsigned  int texture2 = createTextureObj("resources/awesomeface.png", 1, GL_RGBA);

    /**
    * 设置顶点属性指针(位置):
       第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗？它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中，所以这里我们传入0。
       第二个参数指定顶点属性的大小。顶点属性是一个vec3，它由3个值组成，所以大小是3。
       第三个参数指定数据的类型，这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
       第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE，所有数据都会被映射到0（对于有符号型signed数据是-1）到1之间。我们把它设置为GL_FALSE。
       第五个参数叫做步长(Stride)，它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3(6)个float之后，我们把步长设置为3(6) * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的（在两个顶点属性之间没有空隙）我们也可以设置为0来让OpenGL决定具体步长是多少（只有当数值是紧密排列时才可用）。一旦我们有更多的顶点属性，我们就必须更小心地定义每个顶点属性之间的间隔，我们在后面会看到更多的例子（译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节）。
       最后一个参数的类型是void*，所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头，所以这里是0。
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);

    /**
     * 设置顶点属性指针(颜色):
     */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3* sizeof(float)));
    //启用顶点属性
    glEnableVertexAttribArray(1);

    /**
     * 设置顶点属性指针(纹理):
     */
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);


    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0);

    /**
     * 设置图形模式:
     */
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //填充模式
    glUseProgram(shaderProgram.id);
    //设置着色器代码中变量的值
    setUniformValue(shaderProgram.id);
    //渲染循环 使得GLFW在退出之前一直保持运行
    while(!glfwWindowShouldClose(window))
    {
        // 输入控制
        processInput(window);
        //渲染指令
        //当调用glClear函数，清除颜色缓冲之后，整个颜色缓冲都会被填充为glClearColor里所设置的颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

         //bind textures on corresponding texture units
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

       glUseProgram(shaderProgram.id);
        //设置着色器代码中变量的值
       setUniformValue(shaderProgram.id);

        // glBindTexture(GL_TEXTURE_2D, texture);
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        //检查并调用事件，交换缓冲
        glfwSwapBuffers(window); //交换颜色缓冲
        //检查触发事件 函数检查是否有触发事件，比如键盘、鼠标等信号输入，然后更新窗口状态，调用相应的回调函数（可通过回调方法手动设置）。
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    //释放/删除之前的分配的所有资源
    glfwTerminate();
}
int main3(){
    //testGlm();
    show();
    return 0;
}